// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using System.Reflection;
using PostSharp.Extensibility;
using PostSharp.Laos;
using ValidationAspects.Sdk.Interception;

namespace ValidationAspects.PostSharp
{
	[Serializable]
	[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
	[MulticastAttributeUsage(MulticastTargets.Class, AllowMultiple = false)]
	public class ValidateAttribute : CompoundAspect
	{
		public int AspectPriority { get; set; }
		public bool AttributedPropertiesOnly { get; set; }
		public bool AttributedMethodsOnly { get; set; }

		#region Overrides of CompoundAspect
		public override void ProvideAspects(object target, LaosReflectionAspectCollection collection)
		{
			Type type = (Type)target;

			// only apply aspects for methods where their declaring type is the target or does not declare the ValidateAttribute
			foreach (MethodInfo method in
				GetInterceptionValidationMethods.ForType(type, AttributedMethodsOnly).Where(
					m => m.DeclaringType == type || !m.DeclaringType.IsDefined(typeof(ValidateAttribute), false)))
			{
				// perform validator type checking at compilation - create the validators but don't use them
				if (!method.ContainsGenericParameters && !method.IsStatic)
					method.ResolveValidator();

				foreach (ParameterInfo parameterInfo in method.GetParameters().Where(p => !p.IsOut && !p.ParameterType.IsGenericParameter))
				{
					parameterInfo.ResolveValidator();
				}

				collection.AddAspect(GetMethodToAspect(type, method), new MethodValidationAspect {AspectPriority = AspectPriority});
			}

			// only apply aspects for methods where their declaring type is the target or does not declare the ValidateAttribute
			foreach (PropertyInfo property in
				GetInterceptionValidationProperties.ForType(type, AttributedPropertiesOnly).Where(
					p => p.DeclaringType == type || !p.DeclaringType.IsDefined(typeof(ValidateAttribute), false)))
			{
				// perform validator type checking at compilation - create the validators but don't use them
				if (!property.PropertyType.IsGenericParameter)
					property.ResolveValidator();

				collection.AddAspect(GetMethodToAspect(type, property.GetSetMethod(true)),
				                     new PropertyValidationAspect(property.Name) {AspectPriority = AspectPriority});
			}
		}

		private static MethodInfo GetMethodToAspect(Type type, MethodInfo method)
		{
			MethodInfo targetMethod = method;

			// is the method generic and the method's declaring type different to the target
			Type targetType = method.DeclaringType;
			if (targetType != type && targetType.IsGenericType)
			{
				targetType = targetType.GetGenericTypeDefinition(); // required by PostSharp}
				// matchup method
				string methodName = method.Name;
				ParameterInfo[] parameters = method.GetParameters();
				targetMethod =
					targetType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(
						m => methodName == m.Name).First(m =>
						                                 {
						                                 	if (m.GetParameters().Count() != parameters.Count())
						                                 		return false;

						                                 	for (int i = 0; i < parameters.Count(); i++)
						                                 	{
						                                 		Type parameterType = m.GetParameters()[i].ParameterType;
						                                 		if (parameters[i].ParameterType != parameterType && !parameterType.IsGenericParameter)
						                                 			return false;
						                                 	}
						                                 	return true;
						                                 });
			}
			return targetMethod;
		}
		#endregion
	}
}